package sk.uniba.fmph.asdf.tempt;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;

import sk.uniba.fmph.asdf.Exporter;
import sk.uniba.fmph.asdf.Session;
import sk.uniba.fmph.asdf.db.Account;
import sk.uniba.fmph.asdf.exceptions.TemplateException;
import sk.uniba.fmph.asdf.exceptions.TemplateNotInitialized;
import sk.uniba.fmph.asdf.test.RelativePoint;

/**
 *
 * @author Jakub Gaľ
 * @author Marek Timura
 */
public class TemplateBuilder {

	private static boolean isInitialized = false;
	private static PrimaryTestFieldDef selectedField;
	private static int id;
	private static ArrayList<PrimaryTestFieldDef> fields = new ArrayList<>();

	/**
	 * Inicializuje novu sablonu - nastavi id na 0 a vytvori prazdny zoznam
	 * oblasti.
	 */
	public static void initializeTemplate() {
		id = 0;
		isInitialized = true;

		fields.clear();
	}

	/**
	 * Inicializuje novu sablonu na zaklade sablony ulozenej na disku.
	 *
	 * @param filename absolutna cesta k suboru
	 */
	public static void initializeTemplate(String filename) throws TemplateException, IOException {
		initializeTemplate();
		try {
			try (FileInputStream fileIn = new FileInputStream(Session.getUserDir() + Exporter.DIR_TEMPLATES + filename + ".ser"); ObjectInputStream in = new ObjectInputStream(fileIn)) {
				TestTemplate template = (TestTemplate) in.readObject();
				fields = new ArrayList<>(template.getFieldDefs());
				in.close();
				fileIn.close();
			}
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

	public static boolean initialized() {
		return isInitialized;
	}

	/**
	 * Pridanie novej oblasti definovanej lavym hornym a pravym dolnym bodom a
	 * maximalnym poctom dosiahnutelnych bodov.
	 *
	 * @param topLeft lavy horny bod oblasti
	 * @param bottomRight pravy dolny bod oblasti
	 * @param maxPoints maximalny pocet dosiahnutelnych bodov
	 */
	public static void addField(RelativePoint topLeft, RelativePoint bottomRight, int maxPoints) {
		fields.add(new PrimaryTestFieldDef(id++, topLeft, bottomRight, maxPoints));
	}

	/**
	 * Metoda vracia oblasti sablony.
	 *
	 * @return zoznam oblasti
	 */
	public static ArrayList<PrimaryTestFieldDef> getFields() {
		return fields;
	}

	/**
	 * Metoda nastavi oznacenu oblast podla prislusneho bodu. Ak sa bod p
	 * nenachadza v ziadnej oblasti, oznacena oblast nie je ziadna.
	 *
	 * @param p relativny bod
	 */
	public static void selectField(RelativePoint p) {
		selectedField = null;
		for (PrimaryTestFieldDef f : fields) {
			if (f.containsPoint(p)) {
				selectedField = f;
				break;
			}
		}
	}

	/**
	 * Uprava vybratej oblasti. Ak nie je vybrata ziadna oblast, nevykona sa nic.
	 *
	 * @param topLeft nova hodnota laveho horneho rohu
	 * @param bottomRight nova hodnota praveho dolneho rohu
	 * @param maxPoints nova hodnota maximalneho poctu bodov
	 */
	public static void editField(RelativePoint topLeft, RelativePoint bottomRight, int maxPoints) {
		if (selectedField != null) {
			selectedField.setTopLeft(topLeft);
			selectedField.setBottomRight(bottomRight);
			selectedField.setMaxPoints(maxPoints);
		}
	}

	/**
	 * Ak je vybrata oblast, metoda tuto oblast zmaze.
	 */
	public static void deleteField() {
		if (selectedField != null) {
			fields.remove(selectedField);
			selectedField = null;
		}
	}

	/**
	 * Metoda finalizuje sablonu a ulozi ju do suboru.
	 *
	 * @param title nazov sablony
	 * @param author auto sablony
	 * @return vytvorenu sablonu
	 * @throws TemplateNotInitialized ak ziadna sablona nebola inicializovana
	 * @throws IOException ak zlyhala IO operacia
	 */
	public static TestTemplate finalizeTemplate(String title, Account author) throws TemplateNotInitialized, IOException {
		if (isInitialized) {
			TestTemplate finalizedTemplate = new TestTemplate(title, author, fields);
			Exporter.serializeEntity(finalizedTemplate, finalizedTemplate.toString(), Exporter.DIR_TEMPLATES);

			isInitialized = false;
			selectedField = null;
			fields.clear();

			return finalizedTemplate;
		} else {
			throw new TemplateNotInitialized();
		}
	}

	/**
	 * Metoda vracia boolovsku hodnotu podla toho, ci pomyselna oblast tvorena
	 * zadanymi bodmi sa prekryva s uz existujucou oblastou.
	 *
	 * @param topLeft lavy horny roh pomyselnej oblasti
	 * @param bottomRight pravy dolny roh pomyselnej oblasti
	 * @return true ak sa prekryva, v opacnom pripade false
	 */
	public static boolean isOverlaping(RelativePoint topLeft, RelativePoint bottomRight) {
		for (PrimaryTestFieldDef field : fields) {
			if (isContainedRelative(topLeft.x, topLeft.y, field
							.getTopLeft(), field.getBottomRight())
							|| isContainedRelative(bottomRight.x, bottomRight.y, field
							.getTopLeft(), field
							.getBottomRight())
							|| isContainedRelative(topLeft.x, bottomRight.y, field
							.getTopLeft(), field
							.getBottomRight())
							|| isContainedRelative(bottomRight.x, topLeft.y, field
							.getTopLeft(), field
							.getBottomRight())
							|| isContainedRelative(field
							.getTopLeft().x, field
							.getTopLeft().y, topLeft, bottomRight)
							|| isContainedRelative(field.getBottomRight().x,
							field.getBottomRight().y, topLeft, bottomRight)
							|| isContainedRelative(field.getTopLeft().x, field
							.getBottomRight().y, topLeft, bottomRight)
							|| isContainedRelative(field.getBottomRight().x, field
							.getTopLeft().y, topLeft, bottomRight)) {
				return true;
			}
			if ((isContainedRelative(topLeft.x, topLeft.y, field
							.getTopLeft(), field
							.getBottomRight())
							|| isContainedRelative(bottomRight.x, bottomRight.y, field
							.getTopLeft(), field
							.getBottomRight())
							|| isContainedRelative(topLeft.x, bottomRight.y, field
							.getTopLeft(), field
							.getBottomRight()) || isContainedRelative(
							bottomRight.x, topLeft.y, field
							.getTopLeft(),
							field.getBottomRight())
							|| isContainedRelative(field.getTopLeft().x,
							field.getTopLeft().y, topLeft, bottomRight)
							|| isContainedRelative(field.getBottomRight().x, field
							.getBottomRight().y, topLeft, bottomRight)
							|| isContainedRelative(field.getTopLeft().x,
							field.getBottomRight().y, topLeft, bottomRight)
							|| isContainedRelative(field.getBottomRight().x,
							field.getTopLeft().y, topLeft, bottomRight))) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Metoda vracia boolovsku hodnotu podla toho, ci posunuta oblast na poziciu
	 * danu bodmi sa prekryva s uz existujucou oblastou.
	 *
	 * @param topLeft lavy horny roh pomyselnej oblasti
	 * @param bottomRight pravy dolny roh pomyselnej oblasti
	 * @param f posunuta oblast
	 * @return true ak sa prekryva, v opacnom pripade false
	 */
	public static boolean isOverlapingAfterDrag(RelativePoint topLeft, RelativePoint bottomRight, PrimaryTestFieldDef f) {
		for (PrimaryTestFieldDef field : fields) {
			if (!field.equals(f)) {
				if (isContainedRelative(topLeft.x, topLeft.y, field
								.getTopLeft(), field.getBottomRight())
								|| isContainedRelative(bottomRight.x, bottomRight.y, field
								.getTopLeft(), field
								.getBottomRight())
								|| isContainedRelative(topLeft.x, bottomRight.y, field
								.getTopLeft(), field
								.getBottomRight())
								|| isContainedRelative(bottomRight.x, topLeft.y, field
								.getTopLeft(), field
								.getBottomRight())
								|| isContainedRelative(field
								.getTopLeft().x, field
								.getTopLeft().y, topLeft, bottomRight)
								|| isContainedRelative(field.getBottomRight().x,
								field.getBottomRight().y, topLeft, bottomRight)
								|| isContainedRelative(field.getTopLeft().x, field
								.getBottomRight().y, topLeft, bottomRight)
								|| isContainedRelative(field.getBottomRight().x, field
								.getTopLeft().y, topLeft, bottomRight)) {
					return true;
				}
				if ((isContainedRelative(topLeft.x, topLeft.y, field
								.getTopLeft(), field
								.getBottomRight())
								|| isContainedRelative(bottomRight.x, bottomRight.y, field
								.getTopLeft(), field
								.getBottomRight())
								|| isContainedRelative(topLeft.x, bottomRight.y, field
								.getTopLeft(), field
								.getBottomRight()) || isContainedRelative(
								bottomRight.x, topLeft.y, field
								.getTopLeft(),
								field.getBottomRight())
								|| isContainedRelative(field.getTopLeft().x,
								field.getTopLeft().y, topLeft, bottomRight)
								|| isContainedRelative(field.getBottomRight().x, field
								.getBottomRight().y, topLeft, bottomRight)
								|| isContainedRelative(field.getTopLeft().x,
								field.getBottomRight().y, topLeft, bottomRight)
								|| isContainedRelative(field.getBottomRight().x,
								field.getTopLeft().y, topLeft, bottomRight))) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Metoda zisti ci sa relativne zadany bod nachadza medzi lavym hornym a pravym dolnym zadanym bodom
	 * @param x
	 * @param y
	 * @param topLeft
	 * @param bottomRight
	 * @return oblast obsahuje bod
	 */
	private static boolean isContainedRelative(float x, float y, RelativePoint topLeft, RelativePoint bottomRight) {
		return (x >= topLeft.x && x <= bottomRight.x) && (y >= topLeft.y && y <= bottomRight.y);
	}

	/**
	 * Metoda vrati prave oznacenu oblast.
	 *
	 * @return oznacena oblast
	 */
	public static PrimaryTestFieldDef getSelectedField() {
		return selectedField;
	}

	/**
	 * Metoda vrati celkovy pocet bodov.
	 *
	 * @return celkovy pocet bodov
	 */
	public static int getTotalPoints() {
		int result = 0;

		if (isInitialized) {
			for (PrimaryTestFieldDef f : fields) {
				result += f.getMaxPoints();
			}
		}

		return result;
	}

	/**
	 * Metoda vrati celkovy pocet oblasti.
	 *
	 * @return celkovy pocet oblasti
	 */
	public static int getTotalFields() {
		return fields.size();
	}
}
